#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <atomic>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/time.h>
#include <boost/algorithm/string.hpp>

namespace ns_util
{
    class TimeUtil
    {
    public:
        // 获得秒级时间戳 
        static std::string GetTimeStamp()
        {
            // 功能：返回自 Unix 纪元（1970 年 1 月 1 日 00:00:00 UTC）以来的秒数，以字符串形式表示。
            struct timeval _time;
            // struct timeval 是系统定义的结构体，包含两个成员：
                // tv_sec：秒数
                // tv_usec：微秒数（1 秒 = 1,000,000 微秒）
            gettimeofday(&_time, nullptr);// 功能是 获取当前时间，填充到 _time 对象中
            return std::to_string(_time.tv_sec);
        }
        //获得毫秒时间戳
        static std::string GetTimeMs()
        {
            struct timeval _time;
            gettimeofday(&_time, nullptr);
            // 由于我们要获取的是多少毫秒，但是timeval结构体中存的只有秒和微秒，所以我们需要稍微做一下转化
            return std::to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000);
        }
    };

    const std::string temp_path = "./temp/";

    // 在编译的过程中，我们仅仅凭借文件名，往往无法准确定位一个文件，经常需要加上该文件的完整路径，才能实现该文件的精准定位
    // 下面的PathUtil工具类，就是将文件名加上相应的路径前缀和后缀，转化成该文件的完整路径
    class PathUtil
    {
    public:
        static std::string AddSuffix(const std::string &file_name, const std::string &suffix)
        {
            std::string path_name = temp_path;
            path_name += file_name;
            path_name += suffix;
            return path_name;
        }

        // 编译时需要有的临时文件：cpp文件、exe文件、编译报错文件
        // 构建源文件路径+后缀的完整文件名
        // 1234 -> ./temp/1234.cpp

        // 构建cpp文件的完整文件路径
        static std::string Src(const std::string &file_name)
        {
            return AddSuffix(file_name, ".cpp");
            // 返回值：文件路径+文件名file_name+文件后缀.cpp
        }
        
        // 构建可执行程序exe文件的完整路径+后缀名
        static std::string Exe(const std::string &file_name)
        {
            return AddSuffix(file_name, ".exe");
        }

        // 构建编译时报错文件的完整文件路径
        static std::string CompilerError(const std::string &file_name)
        {
            return AddSuffix(file_name, ".compile_error");
        }



        // 运行时需要的临时文件
        static std::string Stdin(const std::string &file_name)
        {
            return AddSuffix(file_name, ".stdin");
        }
        static std::string Stdout(const std::string &file_name)
        {
            return AddSuffix(file_name, ".stdout");
        }
        // 构建该程序对应的标准错误完整的路径+后缀名
        static std::string Stderr(const std::string &file_name)
        {
            return AddSuffix(file_name, ".stderr");
        }
    };

    class FileUtil
    {
    public:
        static bool IsFileExists(const std::string &path_name)
        {
            // struct stat是一个用于存放文件原数据的结构体
            // 如果stat函数按照路径找到了文件，那就会把文件原数据存放到struct stat类型的输出型参数st中
            struct stat st;
            if (stat(path_name.c_str(), &st) == 0)
            {
                //获取属性成功，文件已经存在
                return true;
            }

            return false;
        }

        // 这个函数用来给用户提交的代码起一个名字（要保证名字不能重复）
        static std::string UniqFileName()
        {
            // 如何确保名字的唯一性呢？
            // 我们的想法是采用毫秒级时间戳+原子性递增唯一值: 来保证唯一性

            // 这里我们将id定义为静态变量，这样当第一个线程执行到这条语句时，id 会被初始化为 0 。
            // 当第二个（以及后续更多）线程再执行到这条语句时，由于 id 已经是静态存储期变量且已经完成过初始化了，所以不会再重新置为 0 
            // 而是会直接跳过初始化步骤继续执行后面的代码（比如 id++ 等操作 ）
            static std::atomic_uint id(0);
            // atomic_uint类型本质也是unsigned_int类型
            // 唯一有区别的点在于，所有对 std::atomic_uint 的操作（如读取、写入、修改）都是原子的，即操作在执行过程中不会被其他线程打断。
            id++;
            // 这里的id就表示，每当有一个线程执行一次UniqFileName，id就++一次，这样
            std::string ms = TimeUtil::GetTimeMs();
            // TimeUtil::GetTimeMs用来获取当前的毫秒时间戳
            std::string uniq_id = std::to_string(id);
            return ms + "_" + uniq_id;
        }
        // 这个函数实现的功能就是，在路径target指向的文件中(如果文件不存在则先创建之)，并往里面覆盖写入内容为content的数据
        static bool WriteFile(const std::string &target, const std::string &content)
        {
            // target是一个文件的完整路径，content是要写入target指向文件中的文件具体内容
            // std::ofstream是C++ 标准库中用于文件输出的流类，默认以覆盖模式（std::ios::out）打开文件。
                // 当使用 std::ofstream 以默认模式（std::ios::out）打开文件时：
                    // 若文件不存在：自动创建该文件
                    // 若文件已存在：默认覆盖原有内容（截断为 0 字节）
            std::ofstream out(target);
            // target指向的是一个文件，如果这个文件打开失败，out.is_open(target)就会返回false
            if (!out.is_open())
            {
                // 走到这里就说明target指向的文件打开失败（可能是找不到这个文件，也可能是找到这个文件了，但是没有打开的权限
                return false;
            }
            // 走到这里说明文件已经被成功打开了，现在我们要往target指向的文件里面写数据了
            // 写啥数据呢？写content里面的数据，写多少呢？写content.size()字节
            out.write(content.c_str(), content.size());
            // 写完之后再关闭target指向的文件
            out.close();
            return true;
        }
        // ReadFile()的功能是，读取target指向的文件中的信息，将其写入输出型参数content中
        static bool ReadFile(const std::string &target, std::string *content, bool keep = false)
        {
            // 清空content中以前的残留信息
            (*content).clear();

            // 输入流方式打开文件
            std::ifstream in(target);
            if (!in.is_open())
            {
                // 走到这里说明target指向的文件不存在
                return false;
            }
            // 走到这里说明target指向的文件是存在的，我们现在已经准备好从这个文件中读取数据了
            std::string line;
            // getline的介绍
                // 语法：
                    // 从输入流 is 读取一行到字符串 str，直到遇到分隔符 delim（默认为 '\n'）
                    // std::istream& getline(std::istream& is, std::string& str, char delim);
                    // std::istream& getline(std::istream& is, std::string& str);  // 省略 delim 时默认使用 '\n'
                // 读取规则
                    // 从输入流is中读取字符，直到遇到分隔符（默认是换行符 '\n'）或文件结束符（EOF），将这一行读到的内容写入str中
                    // 不包含分隔符：读取的内容会自动丢弃分隔符（如换行符不会被存入字符串 str）。
                    // 清空目标字符串：在读取前，用于接受读取数据的str 会被清空，即 str.clear()
                // 返回值
                    // 返回输入流 is 的引用。可通过检查流的状态（如 if (getline(is, str))）判断是否成功读取。
                    // 若遇到文件结束符（EOF），流的 eof() 标志会被置为 true。
                    // getline内部重载了强制类型转化
            while (std::getline(in, line))
            {
                (*content) += line;
                // getline默认是不保存行分割符的，但是有些时候确实需要保留\n，所以我们就引入一个标志位参数keep
                // keep为true时，说明调用者想保留\n，否则说明不用 保留
                (*content) += (keep ? "\n" : "");
            }
            in.close();
            return true;
        }
    };

    class StringUtil
    {
    public:
        /************************************
         * SplitString参数介绍
         * str: 输入型参数，表示目标要切分的字符串
         * target: 输出型参数，保存切分完毕的结果
         * sep: 指定的分割符
         * **********************************/
        static void SplitString(const std::string &str, std::vector<std::string> *target, const std::string &sep)
        {
            //调用boost的split函数来切分
            // boost::split介绍
                // 定义
                    // template<typename SequenceContainer, typename Range>
                    // SequenceContainer& split(
                    //     SequenceContainer& Result,
                    //     const Range& Input,
                    //     Predicate Pred,
                    //     token_compress_mode_type eCompress = token_compress_off
                    // );
                // 参数解释
                    // Result：存储分割结果的容器（如 std::vector<std::string>）。
                    // Input：待分割的字符串。
                    // Pred：分隔符判断条件（可以是字符、字符串或谓词函数）。
                    // eCompress：是否压缩连续分隔符（可选，默认不压缩）。
            boost::split((*target), str, boost::is_any_of(sep), boost::algorithm::token_compress_on);
            // boost::is_any_of(sep)解释 
                // sep是SplitString传入的参数，作用是指定分隔符
                // is_any_of(sep)的意思就是，sep是分隔符的集合，sep 中的任意字符都会被当做是分割子串的分隔符
                // 比如boost::is_any_of(",;")  // 逗号和分号都作为分隔符
            
            // boost::algorithm::token_compress_on解释
                // 表示设置压缩连续分隔符：若两个分隔符相邻，会被视为一个。
                // 举个粒子，假如说分隔符是","
                // 现在字符串是“123,,333”，请问你割的时候，是分成三段，中间那段为空，还是说分成左右两端呢？
                // 如果你设置了boost::algorithm::token_compress_on，那就是把中间两个","看做一个分隔符，将字符串分成两段
                // 如果你设置了boost::algorithm::token_compress_off，就是将字符串分成三段，中间那个是空字符串
        }
    };
}